﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using DependencyAnalysis.Internal;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests
{
    [TestClass]
    public class MetricsTest
    {
        private Metrics _metrics;
        private DependencyModel _dependencyModel;

        [TestInitialize]
        public void Initialize()
        {
            _dependencyModel = new DependencyModel();
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(1, 2, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(1, 3, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(2, 3, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(4, 5, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(4, 6, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(4, 7, true, true);
            _dependencyModel.Add(descriptor);
            descriptor = DependencyModelTest.BuildDependencyDescriptor(4, 8, true, true);
            _dependencyModel.Add(descriptor);
            _metrics = new MetricsImplementation(_dependencyModel);
        }

        [TestMethod]
        public void InstabilityForSubmodule()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var instability = _metrics.Instability(descriptor.Source.ImplementingType.Submodule);
            Assert.AreEqual(1d / 3d, instability);
        }

        [TestMethod]
        public void InstabilityForModule()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var instability = _metrics.Instability(descriptor.Target.ImplementingType.Submodule.Module);
            Assert.AreEqual(0.8d, instability);
        }

        [TestMethod]
        public void AfferentCouplingForModule()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.AfferentCoupling(descriptor.Source.ImplementingType.Submodule.Module);
            Assert.AreEqual(2, coupling);
        }

        [TestMethod]
        public void AfferentCouplingForSubmodule()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.AfferentCoupling(descriptor.Source.ImplementingType.Submodule);
            Assert.AreEqual(2, coupling);
        }

        [TestMethod]
        public void AfferentCouplingForType()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.AfferentCoupling(descriptor.Source.ImplementingType);
            Assert.AreEqual(2, coupling);
        }

        [TestMethod]
        public void AfferentCouplingForMember()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.AfferentCoupling(descriptor.Source);
            Assert.AreEqual(2, coupling);
        }

        [TestMethod]
        public void EfferentCouplingForModule()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.EfferentCoupling(descriptor.Target.ImplementingType.Submodule.Module);
            Assert.AreEqual(4, coupling);
        }

        [TestMethod]
        public void EfferentCouplingForSubmodule()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.EfferentCoupling(descriptor.Target.ImplementingType.Submodule);
            Assert.AreEqual(4, coupling);
        }

        [TestMethod]
        public void EfferentCouplingForType()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.EfferentCoupling(descriptor.Target.ImplementingType);
            Assert.AreEqual(4, coupling);
        }

        [TestMethod]
        public void EfferentCouplingForMember()
        {
            var descriptor = DependencyModelTest.BuildDependencyDescriptor(3, 4, true, true);
            var coupling = _metrics.EfferentCoupling(descriptor.Target);
            Assert.AreEqual(4, coupling);
        }

        [TestMethod]
        public void NumberOfVariables()
        {
            var member = (MemberDescriptor)_dependencyModel.DependenciesPerMember.Keys.First();
            member.VariableCount = 3;

            Assert.AreEqual(3, _metrics.NumberOfVariables(member));
        }

        [TestMethod]
        public void NumberOfParameter()
        {
            var member = (MemberDescriptor)_dependencyModel.DependenciesPerMember.Keys.First();
            member.ParameterCount = 2;

            Assert.AreEqual(2, _metrics.NumberOfParameters(member));
        }

        [TestMethod]
        public void NestingDepthOfFieldIsZero()
        {
            var fieldInfo = typeof(InstructionStub).GetField("_field1", BindingFlags.NonPublic | BindingFlags.Instance);
            var memberDescriptor = DescriptorBuilder.BuildMemberDescriptor(fieldInfo);
            memberDescriptor.MemberType = MemberType.Field;

            Assert.AreEqual(0, _metrics.NestingDepth(memberDescriptor));
        }

        [TestMethod]
        public void NestingDepthOfEmptyMethodIsOne()
        {
            var methodInfo = typeof(InstructionStub).GetMethod("NestingMethod", BindingFlags.NonPublic | BindingFlags.Instance);
            var memberDescriptor = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            memberDescriptor.MemberType = MemberType.Method;

            Assert.AreEqual(1, _metrics.NestingDepth(memberDescriptor));
        }

        [TestMethod]
        public void NestingDepthOfPreparedMethodIsThree()
        {
            var methodInfo = typeof(InstructionStub).GetMethod("NestingMethod", BindingFlags.NonPublic | BindingFlags.Instance);
            var memberDescriptor = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            memberDescriptor.MemberType = MemberType.Method;
            _dependencyModel.InstructionCache.SetInstructions(memberDescriptor, new List<Instruction>
                {
                    new Instruction{Offset = 0x0f,OpCode = OperationCode.Brtrue_S,Operand = (long)0x16},
                    new Instruction{Offset = 0x14,OpCode = OperationCode.Br_S,Operand = (long)0x4e},
                    new Instruction{Offset = 0x1b,OpCode = OperationCode.Br_S,Operand = (long)0x3d},
                    new Instruction{Offset = 0x22,OpCode = OperationCode.Brfalse_S,Operand = (long)0x2f},
                    new Instruction{Offset = 0x2d,OpCode = OperationCode.Br_S,Operand = (long)0x35},
                    new Instruction{Offset = 0x48,OpCode = OperationCode.Brtrue_S,Operand = (long)0x1d},
                    new Instruction{Offset = 0x4c,OpCode = OperationCode.Br_S,Operand = (long)0x4e},
                    new Instruction{Offset = 0x4f,OpCode = OperationCode.Ret}
                });

            Assert.AreEqual(3, _metrics.NestingDepth(memberDescriptor));
        }

        [TestMethod]
        public void NumberOfInstructionsForFieldIsZero()
        {
            var fieldInfo = typeof(InstructionStub).GetField("_field1", BindingFlags.NonPublic | BindingFlags.Instance);
            var memberDescriptor = DescriptorBuilder.BuildMemberDescriptor(fieldInfo);
            memberDescriptor.MemberType = MemberType.Field;

            Assert.AreEqual(0, _metrics.NumberOfInstructions(memberDescriptor));
        }

        [TestMethod]
        public void NumberOfInstructionsForMethod()
        {
            var methodInfo = typeof(InstructionStub).GetMethod("NestingMethod", BindingFlags.NonPublic | BindingFlags.Instance);
            var memberDescriptor = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            memberDescriptor.MemberType = MemberType.Method;
            _dependencyModel.InstructionCache.SetInstructions(memberDescriptor, new List<Instruction>
                {
                    new Instruction{Offset = 0x0f,OpCode = OperationCode.Brtrue_S,OperandNonSerialized = (long)0x16},
                    new Instruction{Offset = 0x14,OpCode = OperationCode.Br_S,OperandNonSerialized = (long)0x4e},
                    new Instruction{Offset = 0x1b,OpCode = OperationCode.Br_S,OperandNonSerialized = (long)0x3d},
                    new Instruction{Offset = 0x22,OpCode = OperationCode.Brfalse_S,OperandNonSerialized = (long)0x2f},
                    new Instruction{Offset = 0x2d,OpCode = OperationCode.Br_S,OperandNonSerialized = (long)0x35},
                    new Instruction{Offset = 0x48,OpCode = OperationCode.Brtrue_S,OperandNonSerialized = (long)0x1d},
                    new Instruction{Offset = 0x4c,OpCode = OperationCode.Br_S,OperandNonSerialized = (long)0x4e},
                    new Instruction{Offset = 0x4f,OpCode = OperationCode.Ret}
                });

            Assert.AreEqual(8, _metrics.NumberOfInstructions(memberDescriptor));
        }

        private class InstructionStub
        {
            private readonly int _field1;

            public InstructionStub(int value)
            {
                _field1 = value;
            }

            private void NestingMethod()
            {
            }
        }
    }
}
